#include "xtypes.h"
#include <Python.h>
#include "py3c_compat.h"
#include "utils.h"
#include "bindings.h"
#include "pythonObjects.h"
#include "exceptions.h"

namespace s2e {
  namespace plugins {


    static PyObject* plugins_insertCall(PyObject* self, PyObject* args) {
      PyObject* function  = nullptr;
      PyObject* flag      = nullptr;
      PyObject* routine   = nullptr;

      /* Extract arguments */
      if (PyArg_ParseTuple(args, "|OOO", &function, &flag, &routine) == false) {
        return PyErr_Format(PyExc_TypeError, "s2e::plugins::insertCall(): Invalid number of arguments");
      }

      if (function == nullptr || !PyCallable_Check(function))
        return PyErr_Format(PyExc_TypeError, "s2e::plugins::insertCall(): Expected a function callback as first argument.");

      /* Check if the second arg is an INSERT_POINT*/
      if (flag == nullptr || (!PyLong_Check(flag) && !PyInt_Check(flag)))
        return PyErr_Format(PyExc_TypeError, "s2e::plugins::insertCall(): Expected an INSERT_POINT (integer) as second argument.");

      if (s2e::plugins::python::PyLong_AsUint32(flag) == s2e::plugins::options::CB_BEFORE)
        s2e::plugins::options::callbackBefore = function;

      else if ((s2e::plugins::python::PyLong_AsUint32(flag) == s2e::plugins::options::CB_AFTER))
        s2e::plugins::options::callbackAfter = function;

      else if ((s2e::plugins::python::PyLong_AsUint32(flag) == s2e::plugins::options::CB_FINI))
        s2e::plugins::options::callbackFini = function;

      else if ((s2e::plugins::python::PyLong_AsUint32(flag) == s2e::plugins::options::CB_SYSCALL_ENTRY))
        s2e::plugins::options::callbackSyscallEntry = function;

      else if ((s2e::plugins::python::PyLong_AsUint32(flag) == s2e::plugins::options::CB_SYSCALL_EXIT))
        s2e::plugins::options::callbackSyscallExit = function;

      else
        return PyErr_Format(PyExc_TypeError, "s2e::plugins::insertCall(): Expected an INSERT_POINT (integer) as second argument.");

      Py_INCREF(Py_None);
      return Py_None;
    }

    static PyObject* plugins_Instruction(PyObject* self, PyObject* args) {
      PyObject* opcodes = nullptr;

      /* Extract arguments */
      if (PyArg_ParseTuple(args, "|O", &opcodes) == false) {
        return PyErr_Format(PyExc_TypeError, "Instruction(): Invalid constructor.");
      }

      if (opcodes == nullptr)
        return s2e::plugins::python::PyInstruction();

      if (!PyBytes_Check(opcodes))
        return PyErr_Format(PyExc_TypeError, "Instruction(): Expects bytes as argument.");

      try {
        s2e::uint8* opc  = reinterpret_cast<s2e::uint8*>(PyBytes_AsString(opcodes));
        s2e::uint32 size = static_cast<s2e::uint32>(PyBytes_Size(opcodes));
        return s2e::plugins::python::PyInstruction(opc, size);
      }
      catch (const s2e::exceptions::Exception& e) {
        return PyErr_Format(PyExc_TypeError, "%s", e.what());
      }
    }
    PyMethodDef pluginsCallbacks[] = {
      {"insertCall",                plugins_insertCall,                 METH_VARARGS,   ""},
	  {"Instruction",     (PyCFunction)plugins_Instruction,     		METH_VARARGS,   ""},
      {nullptr,                     nullptr,                            0,              nullptr}
    };

  };
};

